P05-2. L7 트래픽 엔지니어링과 서킷 브레이커 - 실습 검증
Part 05 (실습): L7 트래픽 엔지니어링과 서킷 브레이커 검증
order-api의 신규 버전인order-api-v2파드를 배포하고, 쿠버네티스 표준Gateway API(HTTPRoute) 를Waypoint Proxy에 연결하여 정확히 90:10 비율의 카나리 가중치 분산을 실측한다.- 사내 테스터 전용 헤더(
x-canary: true)가 포함된 요청은 가중치와 무관하게 100%v2파드로만 라우팅되는 헤더 기반 분기를 검증한다. - 카오스 엔지니어링을 위한 결함 주입(
Fault Abort50%HTTP 500&Fault Delay3초 지연) 을 실행해 응답 헤더와 지연 시간을 확인한다. DestinationRule서킷 브레이커(connectionPool) 를 설정하고 동시 병렬 요청(curl --parallel)을 쏘아, 용량 초과 시 백엔드 파드를 보호하기 위해 프록시가 즉시HTTP 503 Service Unavailable(x-envoy-overloaded: true,reset reason: overflow) 로 차단하는 현장을 포착한다.
- P05-1 이론 개념을 먼저 읽고 오면 카나리 배포,
HTTPRoutevsVirtualService,DestinationRule의 서킷 브레이커 원리가 명확히 이해됩니다. - Part 04 실습 마지막 단계에서 인가 정책(
AuthorizationPolicy)을 정리한 상태(part1-demo에order-api,study-client,waypoint가 떠 있는 상태)에서 바로 이어서 진행합니다. - Windows PowerShell 창을 열고 아래 명령어로 현재 파드 상태를 확인합니다.
$env:Path += ";$HOME\bin" cd "$HOME\mesh-study" kubectl get pods -n part1-demo
실습 아키텍처 및 트래픽 제어 흐름 한눈에 보기
flowchart TB
subgraph STEP1_2["Step 1~2 · Gateway API HTTPRoute 기반 카나리 & 헤더 분기"]
direction LR
C1["study-client"] --> WP1["Waypoint Proxy (Envoy L7)"]
WP1 -->|"1. x-canary: true 헤더 있음"| V2_ONLY["order-api-v2 (100%)"]
WP1 -->|"2. 일반 요청 (Weight 90)"| V1_90["order-api-v1 (파드 2개, 90%)"]
WP1 -->|"3. 일반 요청 (Weight 10)"| V2_10["order-api-v2 (파드 1개, 10%)"]
end
subgraph STEP3_4["Step 3~4 · Fault Injection & DestinationRule 서킷 브레이커"]
direction LR
C2["study-client
(5개 동시 병렬 요청!)"] --> WP2["Waypoint Proxy
(maxConnections: 1
http1MaxPendingRequests: 1)"]
WP2 -->|"수용 한도 이내 (1~2개)"| OK["200 OK 정상 처리"]
WP2 -->|"한도 초과 폭주분!"| CB["즉시 차단! 503 Service Unavailable
x-envoy-overloaded: true
(reset reason: overflow / UO)"]
endStep 1. 신규 버전(order-api-v2) 배포 및 HTTPRoute 카나리 규칙 적용
현재 part1-demo에는 version: v1 라벨을 가진 order-api 파드 2개가 떠 있습니다.
여기에 신규 버전인 order-api-v2 디플로이먼트(파드 1개) 와 각 버전을 가리키는 서브 서비스(order-api-v1, order-api-v2), 그리고 쿠버네티스 표준 HTTPRoute (order-api-canary) 를 배포합니다.
HTTPRoute의 parentRefs가 어떻게 Waypoint와 연결되나요?
일반 인그레스 게이트웨이에서는 parentRefs에 kind: Gateway를 적지만, Ambient Mesh 내부(동서 트래픽) 라우팅에서는 parentRefs에 대표 서비스(kind: Service, name: order-api)를 직접 지정합니다! 그러면 그 서비스를 담당하는 Waypoint Proxy가 자동으로 이 HTTPRoute 규칙을 읽어 들여 작동합니다.
# part5-canary.yaml 생성 및 적용
@"
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api-v2
namespace: part1-demo
spec:
replicas: 1
selector:
matchLabels:
app: order-api
version: v2
template:
metadata:
labels:
app: order-api
version: v2
spec:
containers:
- name: echo
image: mendhak/http-https-echo:35
ports:
- containerPort: 8080
env:
- name: HTTP_PORT
value: "8080"
---
apiVersion: v1
kind: Service
metadata:
name: order-api-v1
namespace: part1-demo
spec:
selector:
app: order-api
version: v1
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: order-api-v2
namespace: part1-demo
spec:
selector:
app: order-api
version: v2
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: order-api-canary
namespace: part1-demo
spec:
parentRefs:
- group: ""
kind: Service
name: order-api
port: 80
rules:
- matches:
- headers:
- name: x-canary
value: "true"
backendRefs:
- name: order-api-v2
port: 80
- backendRefs:
- name: order-api-v1
port: 80
weight: 90
- name: order-api-v2
port: 80
weight: 10
"@ | Set-Content -Path "$HOME\mesh-study\part5-canary.yaml" -Encoding utf8
kubectl apply -f "$HOME\mesh-study\part5-canary.yaml"
kubectl wait --for=condition=Ready pod -l version=v2 -n part1-demo --timeout=90s
실제 출력 결과:
deployment.apps/order-api-v2 created
service/order-api-v1 created
service/order-api-v2 created
httproute.gateway.networking.k8s.io/order-api-canary created
pod/order-api-v2-677cff8fd5-wz8sp condition met
Step 2. 90:10 카나리 가중치 분산 및 x-canary: true 헤더 분기 실측!
만약 기본 쿠버네티스 Service였다면 현재 v1 파드가 2개, v2 파드가 1개이므로 20번 요청을 보내면 v2가 33.3%(약 6~7번)를 가져갔을 것입니다.
과연 우리의 Waypoint Proxy가 HTTPRoute에 적힌 weight: 90 vs weight: 10 규칙대로 20번 중 딱 2번(10%)만 v2로 보내는지 확인해 봅니다!
# 1. 일반 고객 요청 20회 연속 전송 후 응답 파드별 횟수(Count) 집계!
kubectl exec -n part1-demo study-client -- curl -s "http://order-api/canary?[1-20]" | Select-String '"hostname": "order-api-' | Group-Object Line | Select-Object Count, Name
# 2. 사내 QA 테스터 헤더(-H "x-canary: true")를 붙여 4회 연속 전송!
kubectl exec -n part1-demo study-client -- curl -s -H "x-canary: true" "http://order-api/canary?[1-4]" | Select-String '"hostname": "order-api-' | ForEach-Object { $_.Line }
실제 출력 결과:
# 1. 일반 요청 20회 집계 결과 (v1 합계: 11 + 7 = 18회 [90%], v2: 2회 [10%]!)
Count Name
----- ----
11 "hostname": "order-api-86d8bb68dd-xh5rm"
2 "hostname": "order-api-v2-677cff8fd5-wz8sp"
7 "hostname": "order-api-86d8bb68dd-mkvrw"
# 2. x-canary: true 헤더 요청 4회 결과 (4회 모두 100% v2 파드로만 이동!)
"hostname": "order-api-v2-677cff8fd5-wz8sp"
"hostname": "order-api-v2-677cff8fd5-wz8sp"
"hostname": "order-api-v2-677cff8fd5-wz8sp"
"hostname": "order-api-v2-677cff8fd5-wz8sp"
- 90:10 가중치 적중: 파드 개수(2:1)와 무관하게 20번의 요청 중
v1파드 두 개가11 + 7 = 18번(90.0%),v2파드가 정확히2번(10.0%)을 처리했습니다! - 헤더 기반 타겟팅 적중:
-H "x-canary: true"헤더를 넣자마자 4번의 요청이 단 한 번의 예외도 없이 100%order-api-v2파드로만 들어갔습니다!
Step 3. 결함 주입(Fault Injection)과 서킷 브레이커(DestinationRule) 배포
이번에는 카오스 엔지니어링과 서킷 브레이커를 검증하기 위해 기존 HTTPRoute를 지우고, Istio의 VirtualService 와 DestinationRule 을 배포합니다.
/chaos경로로 요청하면 50% 확률로 강제HTTP 500에러(fault.abort) 를 터뜨립니다./slow경로로 요청하면 3초 동안 응답을 지연(fault.delay: 3s) 시킵니다.- 동시에
DestinationRule로 최대 동시 연결(maxConnections: 1)과 최대 대기열(http1MaxPendingRequests: 1)을 1개로 극단적으로 조여 놓은 서킷 브레이커를 작동시킵니다!
# 1. 기존 HTTPRoute 삭제 (VirtualService와 충돌 방지)
kubectl delete httproute order-api-canary -n part1-demo
# 2. VirtualService(결함 주입) + DestinationRule(서킷 브레이커) 생성 및 적용
@"
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: order-api-resilience
namespace: part1-demo
spec:
hosts:
- order-api
http:
- match:
- uri:
prefix: /slow
fault:
delay:
percentage:
value: 100
fixedDelay: 3s
route:
- destination:
host: order-api-v1
- match:
- uri:
prefix: /chaos
fault:
abort:
percentage:
value: 50
httpStatus: 500
route:
- destination:
host: order-api-v1
- route:
- destination:
host: order-api-v1
weight: 90
- destination:
host: order-api-v2
weight: 10
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: order-api-cb
namespace: part1-demo
spec:
host: order-api
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: order-api-v1-cb
namespace: part1-demo
spec:
host: order-api-v1
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
"@ | Set-Content -Path "$HOME\mesh-study\part5-resilience.yaml" -Encoding utf8
kubectl apply -f "$HOME\mesh-study\part5-resilience.yaml"
Step 4. 카오스 결함 주입(500 Abort) 및 서킷 브레이커(503 Overflow) 폭발 실측!
먼저 /chaos 경로를 6번 호출해 정말로 절반 가량이 fault filter abort(HTTP 500) 로 떨어지는지 확인하고, 이어서 3초씩 걸리는 /slow 경로에 curl --parallel --parallel-max 5 로 5개의 요청을 동시에 폭격하여 두꺼비집(서킷 브레이커)이 내려가는 장면을 포착합니다!
# 1. /chaos 경로 6회 호출 -> 50% 확률로 HTTP 500 (fault filter abort) 발생 확인!
kubectl exec -n part1-demo study-client -- curl -s -i "http://order-api/chaos?[1-6]" | Select-String "HTTP/1.1|fault filter abort"
# 2. /slow 경로에 5개 동시 병렬 요청(--parallel) 전송 -> 서킷 브레이커 작동(503 x-envoy-overloaded) 확인!
kubectl exec -n part1-demo study-client -- curl -s -i --parallel --parallel-max 5 "http://order-api/slow?[1-5]" | Select-String "HTTP/1.1|x-envoy-overloaded|overflow"
실제 출력 결과:
# 1. /chaos 50% 강제 에러 주입 결과
HTTP/1.1 500 Internal Server Error
fault filter abortHTTP/1.1 200 OK
}HTTP/1.1 500 Internal Server Error
fault filter abortHTTP/1.1 200 OK
}HTTP/1.1 200 OK
}HTTP/1.1 200 OK
# 2. /slow 동시 5개 병렬 요청 시 서킷 브레이커 작동 결과!
HTTP/1.1 200 OK
}HTTP/1.1 200 OK
}HTTP/1.1 503 Service Unavailable
x-envoy-overloaded: true
upstream connect error or disconnect/reset before headers. reset reason: overflowHTTP/1.1 503 Service Unavailable
x-envoy-overloaded: true
upstream connect error or disconnect/reset before headers. reset reason: overflowHTTP/1.1 200 OK
5개의 요청이 동시에 몰려들었을 때, 수용 한도를 넘어선 2개의 요청에 대해 Waypoint Proxy는 백엔드 파드로 요청을 억지로 밀어 넣지 않고 즉시 다음과 같은 응답과 함께 두꺼비집을 내렸습니다:
- 상태 코드:
HTTP/1.1 503 Service Unavailable - 응답 헤더:
x-envoy-overloaded: true(Envoy의 커넥션 풀 한도가 초과되어 서킷 브레이커가 작동했다는 공식 마커!) - 응답 본문:
upstream connect error or disconnect/reset before headers. reset reason: overflow(액세스 로그의UO— Upstream Overflow 플래그와 1:1 매칭!)
덕분에 한도 내에 들어온 나머지 요청들은 3초 뒤 모두 무사히HTTP/1.1 200 OK를 반환받으며 백엔드 서버가 죽지 않고 살아남았습니다!
다음 파트를 위한 리소스 정리
Part 06 실습에 영향을 주지 않도록 방금 테스트한 결함 주입과 서킷 브레이커 규칙을 삭제합니다.
kubectl delete virtualservice,destinationrule --all -n part1-demo
Step 5. 자주 발생하는 문제와 해결 (Troubleshooting)
| 증상 / 에러 메시지 | 원인 | 해결 방법 |
|---|---|---|
HTTPRoute를 적용했는데 가중치(90:10)가 안 먹고 기존처럼 반반씩 나뉨 |
parentRefs에 kind: Gateway를 적었거나, 해당 네임스페이스/서비스에 Waypoint가 연결되지 않음 |
Ambient Mesh 동서 트래픽 제어 시 HTTPRoute의 parentRefs에는 반드시 대표 kind: Service(name: order-api) 를 지정해야 함 |
curl "http://order-api/slow?[1-5]" 를 쳤는데 서킷 브레이커(503)가 안 터지고 5개 모두 200 OK가 나옴 |
--parallel 옵션이 없으면 curl은 1번 요청이 끝난 뒤에야 2번 요청을 순차적으로(1개씩) 보내므로 동시 연결 한도(maxConnections: 1)를 넘지 않음 |
반드시 --parallel --parallel-max 5 옵션을 붙여 5개의 요청이 동시에 들어가도록 해야 서킷 브레이커가 작동함 |
PowerShell에서 Group-Object 실행 후 다음 Select-String 출력이 빈칸으로 보임 |
한 스크립트 블록 안에서 앞선 Group-Object의 테이블 포맷터가 뒤쪽 MatchInfo 객체 출력까지 묶어버림 |
뒤쪽 명령 끝에 | ForEach-Object { $_.Line } 를 붙여 문자열로 꺼내면 깔끔하게 출력됨 |
Step 6. 이번 파트 핵심 명령어 정리 (Command Reference)
# 1. 현재 적용된 Gateway API HTTPRoute 및 Istio VirtualService/DestinationRule 확인
kubectl get httproute,virtualservice,destinationrule -n part1-demo
# 2. 20회 요청 전송 후 응답 파드 비율(Count) 집계
kubectl exec -n part1-demo study-client -- curl -s "http://order-api/canary?[1-20]" | Select-String '"hostname": "order-api-' | Group-Object Line | Select-Object Count, Name
# 3. 동시 병렬 요청(--parallel)으로 서킷 브레이커(x-envoy-overloaded) 작동 테스트
kubectl exec -n part1-demo study-client -- curl -s -i --parallel --parallel-max 5 "http://order-api/slow?[1-5]" | Select-String "HTTP/1.1|x-envoy-overloaded|overflow"
Step 7. 셀프 체크 (Self-Check Q&A)
Q1. 기본 쿠버네티스 Service 환경에서 v1 파드 2개와 v2 파드 1개가 떠 있을 때 트래픽을 90:10으로 나눌 수 없는 이유는 무엇이며, Ambient Mesh는 이를 어떻게 해결하나요?
기본 쿠버네티스 Service는 개별 파드(Endpoint) 단위로 균등 분배하므로 파드가 2:1이면 트래픽도 무조건 66.7% : 33.3%로 나뉩니다. 반면 Ambient Mesh는 Waypoint Proxy에 HTTPRoute(backendRefs의 weight: 90 / 10) 를 걸어 파드 개수와 무관하게 L7 프록시에서 정확한 비율로 트래픽을 쪼개어 보냅니다.
Q2. DestinationRule 서킷 브레이커가 동시 요청 폭주를 감지해 요청을 즉시 차단할 때 반환하는 HTTP 상태 코드, 특수 응답 헤더, 그리고 Envoy 로그 플래그는 무엇인가요?
- HTTP 상태 코드:
503 Service Unavailable - 특수 응답 헤더:
x-envoy-overloaded: true - Envoy 액세스 로그 플래그:
UO(Upstream Overflow)
🔗 다음 파트로 이동
지금까지 우리는 쿠버네티스 클러스터 안쪽에 있는 파드들을 자유자재로 다루었습니다.
하지만 실제 기업 인프라(KT Cloud, NCP, AWS, 온프레미스)에서는 데이터베이스(PostgreSQL)나 레거시 결제 서버가 쿠버네티스 파드가 아닌 외부의 실제 Linux VM(가상머신) 에 떠 있는 경우가 대부분입니다!
다음 Part 06에서는 쿠버네티스 바깥의 Docker 브릿지 네트워크에 실제 PostgreSQL 18 Linux VM 컨테이너(study-pg-vm) 를 띄우고, 이를 WorkloadGroup · WorkloadEntry · ServiceEntry 로 서비스 메시 안에 정식 가족으로 편입시키는 3세대 하이브리드 서비스 메시를 구축합니다!
네비게이션
| 이전 | 목록 | 다음 |
|---|---|---|
| ◀ Part 05 이론 | 목록으로 | Part 06 이론 ▶ |